package org.homeunix.josivan.gwt.tool.gtic;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AgeFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.LogLevel;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

class GwtXmlFileHandler extends Task {

  private final File[] SOURCES_PATH;
  private final File OUTPUT_PATH;
  private final String initialPackage;
  
  private List<File> gwtXmlFiles;
  private Map<File, String> compilableModuleFile;
  private Map<String, File> moduleFile;
  private Map<String, List<String>> compilableModuleDependencies = 
    new HashMap<String, List<String>>();
  
  GwtXmlFileHandler(File[] sources, File out, String initialPackage) {
    this.SOURCES_PATH = sources;
    this.OUTPUT_PATH = out;
    this.initialPackage = initialPackage;
  }
  
  @SuppressWarnings("unchecked")
  @Override
  public void execute() throws BuildException {
    super.execute();
    log("Loading gwt.xml files...");
    
    this.gwtXmlFiles = new LinkedList<File>();
    
    for (File file : this.SOURCES_PATH) {
      this.gwtXmlFiles.addAll(
        FileUtils.listFiles(file, new String[]{"gwt.xml"}, true));
    }
    
    log(this.gwtXmlFiles.size() + " modules found...");
    this.compilableModuleFile = new HashMap<File, String>();
    this.moduleFile = new HashMap<String, File>(this.gwtXmlFiles.size());
    this.doFilterCompilableModules();
  }
  
  private void doFilterCompilableModules() {
    try {
      log("Loading files of all modules found...");
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      DocumentBuilder builder = factory.newDocumentBuilder();
      
      for (File file : this.gwtXmlFiles) {
        String moduleName = extractModuleName(file);
        this.moduleFile.put(moduleName, file);
        Document doc = builder.parse(file);
        NodeList nodes = doc.getElementsByTagName("entry-point");
        int amountEntryPoint = nodes.getLength();
        
        if (amountEntryPoint > 1) {
          log(file.getName() + " more than one entry-point...", LogLevel.ERR.getLevel());
        }
        else if (amountEntryPoint == 1) {
          extractCompilableModuleData(moduleName, doc, nodes);
          this.compilableModuleFile.put(file, moduleName);
        }
      }
      log(this.compilableModuleDependencies.size() + " compilable modules...");
    }
    catch (Exception e) {
      throw new BuildException(e);
    }
  }

  private void extractCompilableModuleData(String moduleName, Document doc, NodeList nodes) {
    for (int i = 0; i < nodes.getLength(); i++) {
      this.compilableModuleDependencies.put(moduleName, new ArrayList<String>());
      extractCompilableModuleDependencies(moduleName, doc);
    }
  }
  
  private void extractCompilableModuleDependencies(String module, Document doc) {
    NodeList nodes = doc.getElementsByTagName("inherits");
    List<String> list = this.compilableModuleDependencies.get(module);
    
    for (int i = 0; i < nodes.getLength(); i++) {
      NamedNodeMap map = nodes.item(i).getAttributes();
      String requiredModule = map.getNamedItem("name").getNodeValue();
      if (requiredModule.startsWith(this.initialPackage)) {
        list.add(requiredModule);
      }
    }
  }
  
  private String extractModuleName(File file) {
    String name = file.getName();
    int dotgwt = name.indexOf(".gwt");
    String path = file.getParent().replace(File.separatorChar, '.');
    int br = path.indexOf(this.initialPackage);
    return path.substring(br).concat(".").concat(name.substring(0, dotgwt));
  }
  
  Map<String, List<String>> getCompilableModuleDependencies() {
    return this.compilableModuleDependencies;
  }

  Map<String, File> getModuleFile() {
    return this.moduleFile;
  }
  
  /*List<File> arquivosGwtXML() {
    return this.arquivosGwtXml;
  }
  */
  
  @SuppressWarnings("unchecked")
  long getMajorDateOfFilesInFolder(File file) {
    List<File> relevantFiles = new LinkedList<File>();
    relevantFiles.add(file);
    relevantFiles.addAll(getFilesFromInheritsModules(file));
    
    List<File> moreRecentFiles = new LinkedList<File>();
    
    for (File relevantFile : relevantFiles) {
      moreRecentFiles.addAll(
        FileUtils.listFiles(
          relevantFile.getParentFile(), 
          new AgeFileFilter(file, false),
          FileFilterUtils.andFileFilter(
            FileFilterUtils.makeSVNAware(null),
            FileFilterUtils.makeCVSAware(null))));
    }

    long time = file.lastModified();
    File moreRecentFile = file;
    for (File moreRecent : moreRecentFiles) {
      if (moreRecent.lastModified() > time) {
        moreRecentFile = moreRecent;
      }
    } 
    
    if (moreRecentFile != file) {
      time = moreRecentFile.lastModified();
    }

    return time;
  }
  
  private List<File> getFilesFromInheritsModules(File file) {
    String moduleName = this.compilableModuleFile.get(file);
    
    if (moduleName == null) {
      return Collections.<File>emptyList();
    }
    
    List<String> inherits = this.compilableModuleDependencies.get(moduleName);
    List<File> inheritsFiles = new LinkedList<File>();
    
    for (String inherit : inherits) {
      inheritsFiles.add(this.moduleFile.get(inherit));
    }
    
    return inheritsFiles;
  }
  
  long getLastCompilationTime(String module) {
    long time = -1;
    File compilationPath = new File(this.OUTPUT_PATH, module); 
  
    if (compilationPath.exists()) {
      File nocache = new File(compilationPath, module + ".nocache.js");
      if (nocache.exists()) {
        return nocache.lastModified();
      }
    }
    
    return time;
  }
}